<?php
/**
 * @author shurik2533
 * Класс отвечающий за работу с пользователями, ролями и за процесс аутентификации и авторизации.
 */
class Default_Model_Managers_UserManager extends Default_Model_Managers_Abstract
{
	/**
	 * Таблица пользователей.
	 * @var Default_Model_DbTable_Users.
	 */
	protected $_usersDbTable;
	
	/**
	 * Таблица ролей
	 * @var Default_Model_DbTable_Roles.
	 */
	protected $_rolesDbTable;
	
	/**
	 * Возвращает таблицу пользователей. LazyLoad
	 * @return Default_Model_DbTable_Users
	 */
	private function getUsersDbTable()
	{
		if ( null === $this->_usersDbTable ) {
			$this->setUsersDbTable('Default_Model_DbTable_Users');
		} 
		
		return $this->_usersDbTable;
	}
	
	/**
	 * устанавливает таблицу пользователей.
	 * @param string | Default_Model_DbTable_Users $userDbTable
	 * @return Default_Model_Managers_UserManager
	 */
	public function setUsersDbTable($usersDbTable)
	{
		if ( is_string($usersDbTable) ) {
			$usersDbTable = new $usersDbTable();
		}
		if ( !$usersDbTable instanceof Default_Model_DbTable_UsersTableInterface) {
			throw new Exception('Invalid users data gateway provided');
		}
		
		$this->_usersDbTable = $usersDbTable;
		return $this;
	}
	
	/**
	 * Возвращает таблицу ролей. LazyLoad
	 * @return Defaul_Model_DbTable_Roles
	 */
	private function getRolesDbTable()
	{
		if ( null === $this->_rolesDbTable ) {
			$this->setRolesDbTable('Default_Model_DbTable_Roles');
		} 
		
		return $this->_rolesDbTable;
	}
	
	/**
	 * устанавливает таблицу ролей.
	 * @param string | Default_Model_DbTable_Roles $rolesDbTable
	 * @return Default_Model_Managers_UserManager
	 */
	private function setRolesDbTable($rolesDbTable)
	{
		if ( is_string($rolesDbTable) ) {
			$rolesDbTable = new $rolesDbTable();
		}
		if ( !$rolesDbTable instanceof Zend_Db_Table_Abstract) {
			throw new Exception('Invalid roles table data gateway provided');
		}
		
		$this->_rolesDbTable = $rolesDbTable;
		return $this;
	}
	
	/**
	 * Возвращает объект Default_Model_User созданный
	 * на основе исходных данных
	 * @param mixed $userData
	 * @return Default_Model_User|null
	 */
	protected function _mapDataToUser($userData)
	{
	    if ($userData === false) {
	        return null;
	    }
		$user = new Default_Model_User(); 
		$user->setId($userData['id'])
		     ->setEmail($userData['email'])
		     ->setLogin($userData['login'])
             ->setFirstName($userData['firstName'])
             ->setLastName($userData['lastName'])
		     ->setRegistrationDate(strtotime($userData['registrationDate']));
		     		     			
        return $user;
	}
	
	/**
	 * Возвращает объект Zend_Acl_Role созданный
	 * на основе входных данных
	 * @param mixed $roleData
	 */
	private function mapDataToRole($roleData)
	{
	    $role = new Zend_Acl_Role($roleData->roles_title);
	    return $role;
	}
	
	
	/**
	 * Проводит процес аутентификации пользователя по предоставленным данным 
	 * электронной почты и пароля. Возвращает TRUE при успешной авторизации,
	 * иначе FALSE
	 * @param string $email
	 * @param string $password
	 * @param bool $rememberMe Запоминать ли авторизованного пользователя
	 * @return bool
	 */
	public function loginByEmailAndPassword($email, $password, $rememberMe = false)
	{
		$authAdapter = new Default_Model_DbTable_Auth_Adapter(
				$this->getUsersDbTable()->getAdapter(),
				$email,
				$password
				);
				
		$auth = Zend_Auth::getInstance();
		$result = $auth->authenticate($authAdapter);
		if ($result->isValid()) {
		    if ( $rememberMe ) {
		        Zend_Session::rememberMe();
		    }
			$user = $this->_mapDataToUser($this->getUsersDbTable()->findUser($authAdapter->getResultId()));

			$storage = $auth->getStorage();
			$storage->write($user);
			return true;
		}
		return false;
	}
	
	/**
	 * Возвращает строку из 8 случайных знаков (Salt)
	 */
	protected function _getRandomSalt()
	{
	    $salt = '';
		for ($i = 0; $i < 8; $i++) {
            $salt .= chr(rand(33, 126));
	    }
	    return $salt;
	}
	
	/**
	 * Выполняет выход текущего пользователя из системы
	 * @return Defualt_Managers_UserManager
	 */
	public function logout()
	{
	    Zend_Session::forgetMe();
		Zend_Auth::getInstance()->clearIdentity();
		return $this;
	}
	
	/**
	 * Возвращает текущего пользователя если пользователь авторизован
	 * иначе возвращает null
	 * @return null | Default_Model_User
	 */
	public function getCurrentUser()
	{
	    $auth = Zend_Auth::getInstance();
		if ($auth->hasIdentity()) {
			return $auth->getIdentity();
		} else {
		    return null;
		}
	}
	
	/**
	 * Возвращает роли текущего пользователся если пользователь авторизован
	 * иначе возвращает 'guest'
	 * @return array
	 */
	public function getCurrentRoles()
	{
	    if ($user = $this->getCurrentUser()) {
	        return $user->getRoles();
	    } else {
	        return array(new Zend_Acl_Role('guest'));
	    }
	}
	
	/**
	 * Проверят разрешен ли текущему пользователю 
	 * доступ к ресурсу с указанными привилегиями
	 * @param string $resource
	 * @param string $privilege
	 * @return bool
	 */
	public function isAllowed($resource, $privilege)
	{
	    foreach ($this->getCurrentRoles() as $role) {
	        if (Zend_Registry::get('acl')->isAllowed($role, $resource, $privilege) ) {
	            return true;
	        }
	    }
	    return false;
	}
	
	/**
	 * Ищет пользователя с соответсвующим полем email.
	 * Если находит возвращает объект Defualt_Model_User, иначе
	 * возвращает null
	 * @param string $email
	 * @return null | Defualt_Model_User
	 */
	public function findUserByEmail($email)
	{
		return $this->_mapDataToUser($this->getUsersDbTable()->findUserByEmail($email));
	}
	
	/**
	 * Ищет пользователя с соответсвующим идентификатором.
	 * Если находит возвращает объект Defualt_Model_User, иначе
	 * возвращает null
	 * @param int $id
	 * @return null | Defualt_Model_User
	 */
	public function findUserById($id)
	{
		return $this->_mapDataToUser($this->getUsersDbTable()->findUser($id));
	}
	
	/**
	 * Ищет роли соответствующие идентификатору пользователя
	 * @param int $userId
	 * @return array of Zend_Acl_Role
	 */
	public function findRolesByUserId($userId)
	{
	    $resultSet = $this->getRolesDbTable()->findByUserId($userId);
	    $entries = array();
		foreach ($resultSet as $row) {
			$entry = $this->mapDataToRole($row);
			$entries[] = $entry;
		}
		return $entries;
	}
	
	/**
	 * Добавление пользователя в базу
	 * @param string 	$email				Электронная почта
	 * @param string 	$login			    Псевдоним
	 * @param string	$firstName      	Имя
	 * @param string	$lastName	        Фамилия
	 * @param int 		$registrationDate	Дата регистрации
	 * @param string 	$password			Пароль
	 * @return int Присвоенный идентификатор
	 */
	public function addUser($email, $login, $firstName, $lastName, $registrationDate, $password)
	{
	    $passwordSalt = $this->_getRandomSalt();
	    $passwordHash = md5(Zend_Registry::get('staticSalt') . md5($password) . $passwordSalt );
	    return $this->getUsersDbTable()->addUser(
	        $email, $login, $firstName, $lastName, $registrationDate, $passwordHash, $passwordSalt
	    );
	}
	
	/**
	 * Изменение данных пользователя в базе
	 * @param int		$id					Идентификатор пользователя
	 * @param string 	$email				Электронная почта
	 * @param string 	$login				Псевдоним
	 * @param string 	$firstName			Имя
	 * @param string	$lastName			Фамилия
	 * @param int 		$registrationDate	Дата регистрации
	 * @return bool Обновлены ли данные
	 */
	public function updateUser($id, $email, $login, $firstName, $lastName, $registrationDate)
	{
	    return $this->getUsersDbTable()->updateUser(
	        $id, $email, $login, $firstName, $lasrName, $registrationDate
	    );
	}
	
	/**
	 * Удаляет данные пользователя из базы
	 * @param int $id Иденетификатор пользователя
	 * @return bool Удалены ли данные
	 */
	public function deleteUser($id)
	{
	    return $this->getUsersDbTable()->deleteUser($id);
	}
	
	/**
	 * Обновляет пароль пользователя
	 * @param int $id Идентификатор пользователя
	 * @param string $password Новый пароль
	 * @return bool обнволены ли данные
	 * @todo: должен ли уровень бизнес логики знать об устройстве хранения пароля? 
	 * 		  возможно корректнее передавать на уровень хранения данных пароль в незашифрованном виде
	 */
	public function updateUserPassword($id, $password)
	{
	    $passwordSalt = $this->_getRandomSalt();
	    $passwordHash = md5(Zend_Registry::get('staticSalt') . md5($password) . $passwordSalt );
	    return $this->getUsersDbTable()->updateUserPassword($id, $passwordHash, $passwordSalt);
	}
	
	/**
	 * Проверяет соответсвие указаного пароля с паролем пользователя
	 * @param int $id Идентификатор пользователя
	 * @param string $password Пароль
	 * @return bool Верен ли пароль
	 */
	public function checkPassword($id, $password)
	{
	    return $this->getUsersDbTable()->checkPassword($id, $password);
	}
	
	/**
	 * Добавляет в базу код потверждения регистрации пользователя
	 * @param int 		$userId 			Идентификатор пользователя
	 * @param string 	$validationCode 	Код подтверждения
	 * @param int 		$validationDeadLine Дата окончания действия кода
	 * @param string 	$passToOldDb 		Пароль для старой базы
	 * @return bool Добавлены ли данные
	 */
	public function addUserValidationCode($userId, $validationCode, $validationDeadLine, $passToOldDb)
	{
	    return $this->getUsersDbTable()->addUserValidationCode($userId, $validationCode, $validationDeadLine, $passToOldDb);
	}
	
	/**
	 * Проверяет наличие кода подтверждения в базе
	 * @param string $validationCode
	 * @return int response: 1 - код найден, и является действительным
	 * 						 0 - код не найден
	 * 						-1 - срок действия когда истек
	 * @throws Exception						
	 */
	public function checkUserValidationCode($validationCode)
	{
	    $codeData = $this->getUsersDbTable()->findUserValidationCode($validationCode);
	    
	    if ( $codeData === false ) {
	        //код в базе не найден
	        return 0;
	    }

	    //если найден код подтверждения проверяем его на актуальность
	    $deadLine = strtotime($codeData['validationDeadLine']);
        
        $userId = (int)$codeData['userId'];
        if ($deadLine > time()) {
            //код найден, и является актуальным
            if ( $userId === 0) {
                throw new Exception('Отсутсвует идентификатор пользователя');
            }
            
            $user = $this->findUserById($userId);
            if ( $user === null) {
                throw new Exception('Отсутсвует пользователь');
            }
            
            //обновляем статус пользователя
            $this->getUsersDbTable()->updateUser($user->getId(), $user->getEmail(), $user->getNickname(), $user->getDateOfBirth(), $user->getRegistrationDate(), $user->getExternalId(), 1);
            //добавляем запись в старую базу
            $this->getExternalUsersDbTable()->addUser($user->getEmail(), $user->getNickname(), $user->getDateOfBirth(), $user->getRegistrationDate(), $codeData['passToOldDb']);
            //удаляем запись кода подтверждения
            $this->getUsersDbTable()->deleteUserValidationCode($user->getId());
            return 1;
        } else {
            //код найден, но срок его действия истек
            //удаляем запись пользователя и кода подтверждения
            $this->getUsersDbTable()->deleteUser($userId);
            $this->getUsersDbTable()->deleteUserValidationCode($userId);
            return -1;
        }
	}
	
	/**
	 * Удаляет все коды подтверждения дата действия которых меньше указаной даты, и соответствующие записи пользователей
	 * @param int $deadLine unix timestamp 
	 * @return int количество удаленных записей
	 */
	public function deleteUsersAndValidationCodes($deadLine)
	{
	    return $this->getUsersDbTable()->deleteUsersAndValidationCodes($deadLine);
	}
	
	/**
	 * Отправляет запрос на подтверждение регистрации пользователя
	 * @todo: избавиться от пароля
	 * @param int $userId Идентификатор пользователя
	 * @param string $password Пароль для старой базы 
 	 * @param int $deadLine срок подтверждения unix timestamp
 	 * @return bool Отправлен ли запрос
	 */
	public function demandUserValidation($userId, $password, $deadLine)
	{
	    $user = $this->findUserById($userId);
	    $validationCode = md5($userId. date('c',time()). date('c',$deadLine));
	    if (!$this->addUserValidationCode($userId, $validationCode, $deadLine, md5($password))) {
	        //ошибка добавления в базу
	        return false;
	    }
	    /* @var $messagesManager Default_Model_Managers_MessagesManager */
	    $messagesManager = Default_Model_GlobalManager::findManager('MessagesManager');
	    
	    $mailer = new Moskva_Utility_Mail();
        $send = $mailer->send($user->getEmail(), $messagesManager->getMessage('managers.user.mail.title'), $messagesManager->getMessage('managers.user.mail.text', array('link'=>'http://new.moskva.com/auth/validate/code/'. $validationCode)));
        if (PEAR::isError($send)) {
            return false; 
        } else { 
            return true;
        }
        
	}
	
    /**
	 * Проверяет наличие пользователей с указанным псевдонимом
	 * @param string $nickname Имя пользователя
	 * @return bool Найдены ли соответсвующие записи
	 */
	public function checkLoginExistance($login)
	{
	    if ( $this->getExternalUsersDbTable()->checkUsernameExistance($login)) {
	        return true;
	    } elseif ( $this->getUsersDbTable()->checkLoginExistance($login)) {
	        return true;
	    } else {
	        return false;
	    }
	}
	
	/**
	 * Проверяет наличие пользователей с указанным адресом электронной почты
	 * @param string $email Электронная почта
	 * @return bool Найдены ли соответсвующие записи
	 */
	public function checkEmailExistance($email)
	{
	    if ( $this->getExternalUsersDbTable()->checkEmailExistance($email)) {
	        return true;
	    } elseif ( $this->getUsersDbTable()->checkEmailExistance($email)) {
	        return true;
	    } else {
	        return false;
	    }
	}
}